package utils

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gorilla/websocket"
	"github.com/nats-io/nats.go"
	"go.uber.org/zap"
	"net/http"
	"strings"
	"sync"
	"time"
)

var (
	WsConnWithConnId sync.Map //基于连接的socket地址

	websocketUpGrader = websocket.Upgrader{
		CheckOrigin: func(r *http.Request) bool {
			//跨域拦截
			requestOrigin := r.Header.Get("Origin")
			if requestOrigin == "" {
				return true
			}
			return true
		},
	}
)

var wsCmdDescription = make(map[WebsocketCmd]string)

type WebsocketCmd string

func (cmd WebsocketCmd) Cmd() string {
	return string(cmd)
}

func (cmd WebsocketCmd) String() string {
	desc, _ := wsCmdDescription[cmd]
	return fmt.Sprintf("[%s]-%s", cmd.Cmd(), desc)
}

func (cmd WebsocketCmd) addDesc(desc string) {
	wsCmdDescription[cmd] = desc
}

// 处理器
var wsHandlers = make(map[WebsocketCmd]WebsocketHandle)

type WebsocketHandle func(currentCtx context.Context, inputMsg []byte, response *WebsocketResponse)

// WebsocketAddHandle 注册处理器
func WebsocketAddHandle(wsCmd WebsocketCmd, handle WebsocketHandle, desc ...string) {
	if len(desc) > 0 {
		wsCmd.addDesc(desc[0])
	}
	if _, ok := wsHandlers[wsCmd]; ok {
		panic(fmt.Errorf("websocket handle for %s already exists", wsCmd.String()))
	}
	wsHandlers[wsCmd] = handle
}

// WebsocketGetHandle 解析处理器
func WebsocketGetHandle(cmdStr string) (wsCmd WebsocketCmd, handle WebsocketHandle, err error) {
	wsCmd = WebsocketCmd(cmdStr)
	var ok bool
	if handle, ok = wsHandlers[wsCmd]; !ok {
		err = errors.New("websocket handle not found")
		return
	}
	return
}

type WsData struct {
	Type int //1.text 2.binary
	Data []byte
}

type WsConn struct {
	connId      string
	conn        *websocket.Conn
	ctx         context.Context
	cancel      context.CancelFunc
	writeChan   chan WsData
	data        sync.Map
	lang        string
	releaseOnce sync.Once
}

// Release 关闭连接并从全局连接中删除
func (wc *WsConn) Release() {
	wc.releaseOnce.Do(func() {
		Logger.Debug("websocket 关闭连接并从全局连接中删除", zap.String("remote", wc.conn.RemoteAddr().String()))
		wc.cancel()
		WsConnWithConnId.Delete(wc.connId)
		close(wc.writeChan)
		if wc.conn != nil {
			if err := wc.conn.Close(); err != nil {
				Logger.Error("关闭wsConn", zap.String("remote", wc.conn.RemoteAddr().String()), zap.Error(err))
			}
		}
	})
}

func (wc *WsConn) GetConnId() string {
	return wc.connId
}

func (wc *WsConn) SetData(key string, data any) {
	wc.data.Store(key, data)
}

func (wc *WsConn) GetData(key string) any {
	if tmp, ok := wc.data.Load(key); ok {
		return tmp
	} else {
		return nil
	}
}

func (wc *WsConn) GetRawConn() *websocket.Conn {
	return wc.conn
}

// StartWait 等待并处理消息
func (wc *WsConn) StartWait() {
	defer wc.Release()
	baseLogger := Logger.With(zap.String("remote", wc.conn.RemoteAddr().String()))
	handleId := uint64(0)
	for {
		_, msgRaw, err := wc.conn.ReadMessage()
		if err != nil {
			baseLogger.Error("websocket接受消息报错", zap.Error(err))
			break
		}
		if strings.ToLower(string(msgRaw)) == "ping" {
			wc.GetResponse().WriteText("pong", "")
			continue
		}
		if len(msgRaw) < 4 {
			baseLogger.Error("websocket接受消息-消息长度不够")
			continue
		}
		//解析处理器
		cmd, msgRaw := msgRaw[:4], msgRaw[4:]
		inputCmd, handle, err := WebsocketGetHandle(string(cmd))
		if err != nil {
			baseLogger.Error("解析处理器", zap.Error(err), zap.ByteString("cmd", cmd), zap.ByteString("msg", msgRaw))
			continue
		}
		//执行处理器
		handleId++
		if handleId == 0 {
			baseLogger.Info("【Websocket】执行处理器处理次数过多，终止连接")
			break
		}
		baseLogger.Debug(fmt.Sprintf("【Websocket】执行处理器[%d]:%s", handleId, inputCmd.String()), zap.ByteString("msg", msgRaw))
		func() {
			defer func() {
				if r := recover(); r != nil {
					baseLogger.Error("【Websocket】执行处理器异常", zap.Any("error", r), zap.Stack("stack"))
				}
			}()
			handle(wc.ctx, msgRaw, wc.GetResponse(handleId))
		}()
	}
}

func (wc *WsConn) GetResponse(handleId ...uint64) *WebsocketResponse {
	response := &WebsocketResponse{
		WsConn: wc,
	}
	if len(handleId) > 0 && handleId[0] > 0 {
		response.HandleId = handleId[0]
	}
	return response
}

// WebsocketWsConnNew 升级到websocket
func WebsocketWsConnNew(w http.ResponseWriter, r *http.Request) (wsConn *WsConn, err error) {
	r.Context()
	conn, err := websocketUpGrader.Upgrade(w, r, nil)
	if err != nil {
		Logger.Error("升级到websocket失败", zap.Error(err))
		return
	}
	ctx, cancel := context.WithCancel(context.Background())
	wsConn = &WsConn{
		connId:    GetEnv() + "_" + UniqueId(),
		conn:      conn,
		ctx:       ctx,
		cancel:    cancel,
		writeChan: make(chan WsData, 1024),
		lang:      Lang(r.Context()),
	}
	baseLogger := Logger.With(zap.String("remote", wsConn.conn.RemoteAddr().String()))
	//设置超时读取时间
	if err = conn.SetReadDeadline(time.Now().Add(time.Minute)); err != nil {
		baseLogger.Error("增加读取时间失败", zap.Error(err))
	}
	//设置ping响应
	conn.SetPingHandler(func(appData string) error {
		Logger.Debug("收到ping响应", zap.String("remote", wsConn.conn.RemoteAddr().String()))
		return conn.WriteControl(websocket.PongMessage, nil, time.Now().Add(time.Second))
	})
	//设置pong响应
	conn.SetPongHandler(func(string) error {
		//Logger.Debug("收到pong响应", zap.String("remote", wsConn.conn.RemoteAddr().String()))
		return conn.SetReadDeadline(time.Now().Add(time.Minute))
	})
	//设置关闭处理
	conn.SetCloseHandler(func(code int, text string) error {
		wsConn.Release()
		baseLogger.Debug("websocket收到关闭通知", zap.Int("code", code), zap.String("text", text))
		return nil
	})
	// 写入消息
	go func() {
		defer func() {
			if err := recover(); err != nil {
				baseLogger.Error("websocket写入异常", zap.Any("err", err), zap.Stack("stack"))
			}
		}()
		for {
			select {
			case <-ctx.Done():
				return
			case writeMsg, ok := <-wsConn.writeChan:
				if !ok {
					return
				}
				//if writeMsg.Type == websocket.TextMessage {
				//	Logger.Debug("写入消息", zap.String("data", string(writeMsg.Data)))
				//}
				err = conn.WriteMessage(writeMsg.Type, writeMsg.Data)
				if errors.Is(err, websocket.ErrCloseSent) {
					Logger.Error("写入消息失败,客户端已关闭")
					return
				} else if err != nil {
					baseLogger.Error("写入消息失败", zap.Error(err))
				}
			}
		}
	}()
	//定时发送ping
	go func() {
		t := time.NewTicker(time.Second * 30)
		defer t.Stop()
		for {
			select {
			case <-ctx.Done():
				return
			case <-t.C:
				if err = wsConn.conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(time.Second*5)); err != nil {
					Logger.Error("wsConn发送ping失败", zap.String("remote", wsConn.conn.RemoteAddr().String()), zap.Error(err))
					//关闭
					wsConn.Release()
				}
			}
		}
	}()

	WsConnWithConnId.Store(wsConn.connId, wsConn)
	return wsConn, nil
}

// WebsocketResponse 响应websocket
type WebsocketResponse struct {
	WsConn   *WsConn
	HandleId uint64
}

// SingleWriteCheck websocket单例写入检查
func (wr *WebsocketResponse) SingleWriteCheck(cmd WebsocketCmd) bool {
	if wr.HandleId == 0 {
		return true
	}
	lastHandleId := uint64(0)
	lastHandleKey := fmt.Sprintf("last_handle_id:%s", cmd.Cmd())
	if tmp := wr.WsConn.GetData(lastHandleKey); tmp != nil {
		lastHandleId, _ = tmp.(uint64)
	}
	if wr.HandleId < lastHandleId {
		Logger.Info("websocket单例写入检查-已有其他协程执行，本次response中断", zap.Uint64("我的handleId", wr.HandleId), zap.Uint64("lastHandleId", lastHandleId), zap.String("remote", wr.WsConn.conn.RemoteAddr().String()))
		return false
	}
	if wr.HandleId > lastHandleId {
		wr.WsConn.SetData(lastHandleKey, wr.HandleId)
	}
	return true
}

// StopOtherWriteCmd 停止其他协程的相关命令输出
func (wr *WebsocketResponse) StopOtherWriteCmd(cmds ...WebsocketCmd) {
	for _, cmd := range cmds {
		lastHandleKey := fmt.Sprintf("last_handle_id:%s", cmd.Cmd())
		if tmp := wr.WsConn.GetData(lastHandleKey); tmp != nil {
			lastHandleId, _ := tmp.(uint64)
			if lastHandleId > 0 {
				wr.WsConn.SetData(lastHandleKey, lastHandleId+1)
			}
		}
	}
}

func (wr *WebsocketResponse) WriteText(cmd WebsocketCmd, msg string, singleWriteCheck ...bool) {
	//Logger.Debug("写入文本", zap.String("msg", msg), zap.Stack("stack"))
	//单写检查
	if !(len(singleWriteCheck) > 0 && singleWriteCheck[0] == false) && !wr.SingleWriteCheck(cmd) {
		return
	}
	err := Cover(func() {
		wr.WsConn.writeChan <- WsData{Type: websocket.TextMessage, Data: []byte(cmd.Cmd() + msg)}
	})
	if err != nil {
		Logger.Error("websocket-WriteText失败", zap.Error(err), zap.String("cmd", cmd.String()))
	}
}

func (wr *WebsocketResponse) WriteJson(cmd WebsocketCmd, data interface{}, singleWriteCheck ...bool) {
	//单写检查
	if !(len(singleWriteCheck) > 0 && singleWriteCheck[0] == false) && !wr.SingleWriteCheck(cmd) {
		return
	}

	msg, err := json.Marshal(data)
	if err != nil {
		Logger.Error("websocket-WriteJson", zap.String("remote", wr.WsConn.conn.RemoteAddr().String()), zap.Error(err))
		return
	}
	err = Cover(func() {
		wr.WsConn.writeChan <- WsData{Type: websocket.TextMessage, Data: append([]byte(cmd.Cmd()), msg...)}
	})
	if err != nil {
		Logger.Error("websocket-WriteJson失败", zap.Error(err), zap.String("cmd", cmd.String()))
	}
}

func (wr *WebsocketResponse) WriteBinary(cmd WebsocketCmd, msg []byte, singleWriteCheck ...bool) {
	//单写检查
	if !(len(singleWriteCheck) > 0 && singleWriteCheck[0] == false) && !wr.SingleWriteCheck(cmd) {
		return
	}
	err := Cover(func() {
		wr.WsConn.writeChan <- WsData{Type: websocket.BinaryMessage, Data: append([]byte(cmd.Cmd()), msg...)}
	})
	if err != nil {
		Logger.Error("websocket-WriteBinary失败", zap.Error(err), zap.String("cmd", cmd.String()))
	}
}

func (wr *WebsocketResponse) WriteRaw(msg []byte) {
	err := Cover(func() {
		wr.WsConn.writeChan <- WsData{Type: websocket.BinaryMessage, Data: msg}
	})
	if err != nil {
		Logger.Error("websocket-WriteRaw失败", zap.Error(err))
	}
}

func (wr *WebsocketResponse) WriteErr(cmd WebsocketCmd, err error, singleWriteCheck ...bool) {
	//单写检查
	if !(len(singleWriteCheck) > 0 && singleWriteCheck[0] == false) && !wr.SingleWriteCheck(cmd) {
		return
	}
	var i18nErr ErrorI18n
	errMsg := err.Error()
	if errors.As(err, &i18nErr) {
		errMsg = i18nErr.ErrorWithLang(wr.WsConn.lang)
	}
	Logger.Error("返回ws错误给客户端", zap.String("cmd", cmd.String()), zap.String("remote", wr.WsConn.conn.RemoteAddr().String()), zap.Error(err))
	writeErr := Cover(func() {
		wr.WsConn.writeChan <- WsData{Type: websocket.TextMessage, Data: []byte(cmd.Cmd() + errMsg)}
	})
	if writeErr != nil {
		Logger.Error("websocket-WriteText失败", zap.Error(writeErr), zap.String("cmd", cmd.String()))
	}
}

// WebsocketWsConnGet 根据connId获取conn
func WebsocketWsConnGet(connId string) (wsConn *WsConn, exist bool) {
	val, exist := WsConnWithConnId.Load(connId)
	if !exist {
		return nil, false
	}
	return val.(*WsConn), true
}

// WebsocketListenWriteWithNats 基于nats监听需要发送到websocket客户端的消息
func WebsocketListenWriteWithNats(nc *nats.Conn, subject string) (err error) {
	Logger.Info("启动基于nats监听需要发送到websocket客户端的消息")
	_, err = nc.Subscribe(subject, func(msg *nats.Msg) {
		connId := msg.Header.Get("conn_id")
		cmd := msg.Header.Get("cmd")
		dataType := msg.Header.Get("data_type")
		wsConn, exist := WebsocketWsConnGet(connId)
		if !exist {
			return
		}
		switch dataType {
		case "text":
			wsConn.GetResponse().WriteText(WebsocketCmd(cmd), string(msg.Data))
		case "binary":
			wsConn.GetResponse().WriteBinary(WebsocketCmd(cmd), msg.Data)
		}
	})
	return err
}
